<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="tool" content="leanote-desktop-app">
<title>TCP</title>
<style>
.tab{font-size:12px; margin-bottom: 10px;}
.tab a{cursor:pointer;cursor:pointer;display:inline-block;margin-right:10px;color:#000}#tab-html{color:#ccc}

.content-container .content-html{visibility: hidden;}
.content-container.html .content-markdown{display:none}
.content-container.html .content-html{display:block; visibility: visible;}
.content-container.html #tab-markdown{color:#ccc}
.content-container.html #tab-html{color:#000}
.content-markdown {overflow: auto;}
textarea {display: none;}

*{font-family:"lucida grande","lucida sans unicode",lucida,helvetica,"Hiragino Sans GB","Microsoft YaHei","WenQuanYi Micro Hei",sans-serif;}

body {
  margin: 0;
}

/*公用文字样式*/
h1{font-size:30px}h2{font-size:24px}h3{font-size:18px}h4{font-size:14px}
.note-container{
    width:850px; 
    margin:auto;
    padding: 10px 20px;
    box-shadow: 1px 1px 10px #eee;
}
#title {
  margin: 0;
}
table {
    margin-bottom: 16px;
    border-collapse: collapse;
}
table th, table td {
    padding: 6px 13px;
    border: 1px solid #ddd;
}
table th {
    font-weight: bold;
}

table tr {
    background-color: none;
    border-top: 1px solid #ccc;
}
table tr:nth-child(2n) {
    background-color: rgb(247, 247, 249);
}
.mce-item-table, .mce-item-table td, .mce-item-table th, .mce-item-table caption {
  border: 1px solid #ddd;
  border-collapse: collapse;
  padding: 6px 13px;
}
blockquote {
  border-left-width:10px;
  background-color:rgba(128,128,128,0.05);
  border-top-right-radius:5px;
  border-bottom-right-radius:5px;
  padding:15px 20px;
  border-left:5px solid rgba(128,128,128,0.075);
}
blockquote p {
  margin-bottom:1.1em;
  font-size:1em;
  line-height:1.45
}
blockquote ul:last-child,blockquote ol:last-child {
  margin-bottom:0
}
pre {
  padding: 18px;
  background-color: #f7f7f9;
  border: 1px solid #e1e1e8;
  border-radius: 3px;
  display: block;
}
code {
  padding: 2px 4px;
  font-size: 90%;
  color: #c7254e;
  white-space: nowrap;
  background-color: #f9f2f4;
  border-radius: 4px;
}
.footnote {
  vertical-align: top;
  position: relative;
  top: -0.5em;
  font-size: .8em;
}

hr {
  margin:2em 0
}
img {
  max-width:100%;
  display: block;
  margin: auto;
}
pre {
  word-break:break-word
}
p,pre,pre.prettyprint,blockquote {
  margin:0 0 1.1em
}
hr {
  margin:2em 0
}
img {
  max-width:100%
}
.sequence-diagram,.flow-chart {
  text-align:center;
  margin-bottom:1.1em
}
.sequence-diagram text,.flow-chart text {
  font-size:15px !important;
  font-family:"Source Sans Pro",sans-serif !important
}
.sequence-diagram [fill="#ffffff"],.flow-chart [fill="#ffffff"] {
  fill:#f6f6f6
}
.sequence-diagram [stroke="#000000"],.flow-chart [stroke="#000000"] {
  stroke:#3f3f3f
}
.sequence-diagram text[stroke="#000000"],.flow-chart text[stroke="#000000"] {
  stroke:none
}
.sequence-diagram [fill="#000"],.flow-chart [fill="#000"],.sequence-diagram [fill="#000000"],.flow-chart [fill="#000000"],.sequence-diagram [fill="black"],.flow-chart [fill="black"] {
  fill:#3f3f3f
}
ul,ol {
  margin-bottom:1.1em
}
ul ul,ol ul,ul ol,ol ol {
  margin-bottom:1.1em
}
kbd {
  padding:.1em .6em;
  border:1px solid rgba(63,63,63,0.25);
  -webkit-box-shadow:0 1px 0 rgba(63,63,63,0.25);
  box-shadow:0 1px 0 rgba(63,63,63,0.25);
  font-size:.7em;
  font-family:sans-serif;
  background-color:#fff;
  color:#333;
  border-radius:3px;
  display:inline-block;
  margin:0 .1em;
  white-space:nowrap
}
.toc ul {
  list-style-type:none;
  margin-bottom:15px
}
.m-todo-item {
  list-style: none;
}
pre code {
  padding: 0;
  color: inherit;
  white-space: pre-wrap;
  background-color: inherit;
  border-radius: 0;
}
</style>
<!-- 该css供自定义样式 -->
<link href="../leanote-markdown.css" rel="stylesheet">
</head>

<body>

	<div class="note-container">
		<h1 class="title" id="leanote-title">TCP</h1>
		<div class="content-container html" id="content-container">
			<!-- 切换 -->
			<div class="tab"><a id="tab-markdown">Markdown</a><a id="tab-html">HTML</a></div>
			<textarea id="leanote-content-markdown">[TOC]

----------


TCP充分实现了数据传输时各种控制功能，根据TCP这些机制，在IP这种无连接的网络上也能够实现高可靠性的通信。

----------


# TCP的特点及其目的

TCP通过校验和、序列号、确认应答、重发机制、连接管理以及窗口控制等机制实现可靠性传输。

----------


# 通过序列号与确认应答提高可靠性

在TCP中，当发送端数据到达接受主机时，接收端主机会返回一个已收到的消息的通知。这个消息叫做确认应答（ACK Positive Acknowledgement）。

- 正常的数据传输：
![](TCP_files/5976a6d1ab644135b40008aa.png)

- 非正常的数据传输
    - 在一定时间内没有等到确认应答，发送端就可以认为数据已经丢失，并进行重发。
![](TCP_files/5976a6d1ab644135b40008a9.png)
    - 未确认应答并不意味着数据一定丢失。也有可能是数据对方已经收到，只是返回的确认应答在途中丢失。
![](TCP_files/5976a6d1ab644135b40008ab.png)

这些确认应答处理、重发控制以及重复控制等功能都可以通过序列号实现。序列号是按照顺序给发送数据的每个字节（8位字节）都标上号码的编号（序列号的初始值并非为0。而是在建立连接以后由随机数生成。而后面的计算则是对每一字节加一）。接收端查询接收数据TCP首部中序列号和数据的长度，将自己下一步应该接收的序列号作为确认硬代返送回去。这样，通过序列号和确认应答号，TCP可以实现可靠传输。

序列号（或确认应答号）也指字节与字节之间的分隔。

TCP的数据长度并未写入TCP首部。实际通信中求得TCP包的长度的计算公式是：IP首部中的数据包长度-IP首部长度TCP首部长度。


![](TCP_files/5976b2a0ab64413383000a7d.png)

----------


# 重发超时如何确定

TCP时时刻刻都要保持高可靠的通讯。它在每次发包时都会计算``往返时间（RTT Round Trip Time 指报文段的往返时间）`及`偏差（RTT时间波动的值，方差。有时也叫抖动）`。将这个往返时间和偏差相加重发超时的时间，就是比这个综合要稍大一点的值。

> 如果数据被超时重传后还是接收不到应答,则要进行再次发送。此时，等待确认的时间会以2倍，4倍的指数函数延长。

----------


# 连接管理

TCP的一个连接的建立至少需要来回发送7个包才能完成。
![](TCP_files/5976b660ab64413383000b1f.png)

----------


# TCP以段为单位发送数据

在建立TCP连接的同时，也可以确定发送数据包的单位，我们也可以称其为“最大消息长度”（MSS：Maximum Segment Size）。最理想的情况是，最大消息长度正好是IP中不会被分片处理的最大数据长度。

TCP在传输大朗数据时，是以MSS的大小将数据进行分割发送的。进行重发时也是以MSS为单位。

两端的主机在发出建立连接的请求时，会在TCP首部中写入MSS选项，告诉对方自己的接口能够适应的MSS的大小（为附加MSS选项，TCP首部将不再是20字节，而是4字节的整数倍）。然后会在两者之间选择一个较小的值来使用（在建立连接时，如果某一方的MSS选项被省略，可以选为IP包的长度不超过576字节的值（IP首部20字节，TCP首部20字节，MSS 536字节））。

![](TCP_files/5976e4e9ab644135b40010e2.png)

----------


# 窗口控制提高速度
窗口大小就是指无需等待确认应答而可以继续发送数据的最大值。如下图中，窗口大小为4个段。

这个机制实现了使用大量的缓冲区（Buffer 在此处标识临时保存收发数据的场所。通常是在计算机内存中开辟的一部分空间），通过对多个段同时进行确认应答的功能。
![](TCP_files/5976e4e8ab644135b40010df.png)

在滑动窗口以外的部分包括尚未发送的数据以及已经确认对端已收到的数据。当数据发出后若如期收到确认应答就可以不用再重发，此时数据皆可以从缓冲区清除。

收到确认应答，将窗口滑动到确认硬代中的需要的位置。这样可以顺序地将多个段同时发送提高通信性能。这种机制也被称为滑动窗口控制。

![](TCP_files/5976e4e9ab644135b40010e4.png)

----------


# 窗口控制和重发控制
- 考虑确认应答未能返回的情况。
这种情况下，数据已经达到对端，是不需要进行重发的。然而，在没有使用窗口控制的时候，没有收到确认应答的数据会被重发。而使用了窗口控制，如下图，某些确认应答即便丢失也无需重发。
![](TCP_files/5976e4e8ab644135b40010e0.png)

- 某个报文段丢失的情况。
接收主机如果收到一个自己应该接收的序号以外的数据时，会针对当前位置收到数据返回确认应答（不过即使接收端主机收到的包序号并不连续，也不会将数据丢弃而是暂时保存至缓冲区中）。
当某一报文段丢失后，发送端会一直收到序号为1001的确认应答，这个确认应答好像在提醒发送端“我想接收的是从1001开始的数据”。因此，在窗口比较大，又出现报文段丢失的情况下，同一个序号的确认应答将会被重复不断地返回。而发送端主机如果连续3次收到同一个确认应答（之所以连续收到3次而不是两次的理由是因为，即使数据段的序号被替换两次也不会触发重发机制）。就会将其所对应的数据进行重发。这种机制比之前提到的超时管理更加高效，因此也被称作高速重发控制。
![](TCP_files/5976e4e8ab644135b40010de.png)

----------


# 流控制

发送端根据自己的实际情况发送数据。但是，接收端可能收到的是一个毫无关系的数据包有可能会在处理其他问题上花费一些时间。因此在为这个数据包做其他处理时会耗费一些时间，甚至在高负荷情况下无法接收任何数据。如此一来，如果接收端将本应该接收的数据丢弃的话，就又会触发重发机制，从而导致网络流量的浪费。

为了防止这种现象发生，TCP提供一种机制可以让发送端根据接收端的实际接收能力控制发送的数据量。这就是所谓的流控制。它的具体操作时，接收端主机向发送端主机通知自己可以接收数据的大小，于是发送端会发送不超过这个限制的数据。该大小限度就被称为窗口大小。

TCP首部中，专门有一个字段用来通知窗口大小。接收主机将自己的可以接收的缓冲区大小放入这个字段通知给发送端。这个值越大，说明网络的吞吐量越高。

不过，接收端这个缓冲区一旦面临数据溢出时，窗口大小的值也会随之被设置为一个更小的值通知给发送端，从而控制数据发送量。就是说，发送端主机会根据接收端主机的指示，对发送数据的量进行控制。这也形成了一个完整的TCP流控制（流量控制）。
 ![](TCP_files/5976e4e9ab644135b40010e3.png)

----------


# 拥塞控制
网络中的链路容量和交换结点中的缓存和处理机都有着工作的极限，当网络的需求超过它们的工作极限时，就出现了拥塞。拥塞控制就是防止过多的数据注入到网络中，这样可以使网络中的路由器或链路不致过载。常用的方法就是：

 1. 慢开始、拥塞控制
 2. 快重传、快恢复

一切的基础还是慢开始，这种方法的思路是这样的：

- 发送方维持一个叫做“拥塞窗口”的变量，该变量和接收端口共同决定了发送者的发送窗口；
- 当主机开始发送数据时，避免一下子将大量字节注入到网络，造成或者增加拥塞，选择发送一个1字节的试探报文；
- 当收到第一个字节的数据的确认后，就发送2个字节的报文；
- 若再次收到2个字节的确认，则发送4个字节，依次递增2的指数级；
- 最后会达到一个提前预设的“慢开始门限”，比如24，即一次发送了24个分组，此时遵循下面的条件判定：
    - cwnd < ssthresh， 继续使用慢开始算法；
    - cwnd > ssthresh，停止使用慢开始算法，改用拥塞避免算法；
    - cwnd = ssthresh，既可以使用慢开始算法，也可以使用拥塞避免算法；
- 所谓拥塞避免算法就是：每经过一个往返时间RTT就把发送方的拥塞窗口+1，即让拥塞窗口缓慢地增大，按照线性规律增长；
- 当出现网络拥塞，比如丢包时，将慢开始门限设为原先的一半，然后将cwnd设为1，执行慢开始算法（较低的起点，指数级增长）；
![](TCP_files/5976fe74ab64413383001469.png)

上述方法的目的是在拥塞发生时循序减少主机发送到网络中的分组数，使得发生拥塞的路由器有足够的时间把队列中积压的分组处理完毕。慢开始和拥塞控制算法常常作为一个整体使用，而快重传和快恢复则是为了减少因为拥塞导致的数据包丢失带来的重传时间，从而避免传递无用的数据到网络。快重传的机制是：

- 接收方建立这样的机制，如果一个包丢失，则对后续的包继续发送针对该包的重传请求；
- 一旦发送方接收到三个一样的确认，就知道该包之后出现了错误，立刻重传该包；
- 此时发送方开始执行“快恢复”算法：
    - 慢开始门限减半；
    - cwnd设为慢开始门限减半后的数值；
    - 执行拥塞避免算法（高起点，线性增长）；
![](TCP_files/5976fe8fab64413383001471.png)

# 提高网络利用率的规范
## Nagle算法(转自[Nagle 算法](http://blog.163.com/li_xiang1102/blog/static/607140762011111103213616/))
TCP/IP协议中，无论发送多少数据，总是要在数据前面加上协议头，同时，对方接收到数据，也需要发送ACK表示确认。为了尽可能的利用网络带宽，TCP总是希望尽可能的发送足够大的数据。（一个连接会设置MSS参数，因此，TCP/IP希望每次都能够以MSS尺寸的数据块来发送数据）。Nagle算法就是为了尽可能发送大块数据，避免网络中充斥着许多小数据块。

> Nagle算法的基本定义是任意时刻，最多只能有一个未被确认的小段。 所谓“小段”，指的是小于MSS尺寸的数据块，所谓“未被确认”，是指一个数据块发送出去后，没有收到对方发送的ACK确认该数据已收到。

Nagle算法的规则（可参考tcp_output.c文件里tcp_nagle_check函数注释）：

    如果包长度达到MSS，则允许发送；
    如果该包含有FIN，则允许发送；
    设置了TCP_NODELAY选项，则允许发送；
    未设置TCP_CORK选项时，若所有发出去的小数据包（包长度小于MSS）均被确认，则允许发送；
    上述条件都未满足，但发生了超时（一般为200ms），则立即发送。


> Nagle算法只允许一个未被ACK的包存在于网络，它并不管包的大小，因此它事实上就是一个扩展的停-等协议，只不过它是基于包停-等的，而不是基于字节停-等的。Nagle算法完全由TCP协议的ACK机制决定，这会带来一些问题，比如如果对端ACK回复很快的话，Nagle事实上不会拼接太多的数据包，虽然避免了网络拥塞，网络总体的利用率依然很低。



</textarea>
			<!-- markdown -->
			<pre class="content-markdown">[TOC]

----------


TCP充分实现了数据传输时各种控制功能，根据TCP这些机制，在IP这种无连接的网络上也能够实现高可靠性的通信。

----------


# TCP的特点及其目的

TCP通过校验和、序列号、确认应答、重发机制、连接管理以及窗口控制等机制实现可靠性传输。

----------


# 通过序列号与确认应答提高可靠性

在TCP中，当发送端数据到达接受主机时，接收端主机会返回一个已收到的消息的通知。这个消息叫做确认应答（ACK Positive Acknowledgement）。

- 正常的数据传输：
![](TCP_files/5976a6d1ab644135b40008aa.png)

- 非正常的数据传输
    - 在一定时间内没有等到确认应答，发送端就可以认为数据已经丢失，并进行重发。
![](TCP_files/5976a6d1ab644135b40008a9.png)
    - 未确认应答并不意味着数据一定丢失。也有可能是数据对方已经收到，只是返回的确认应答在途中丢失。
![](TCP_files/5976a6d1ab644135b40008ab.png)

这些确认应答处理、重发控制以及重复控制等功能都可以通过序列号实现。序列号是按照顺序给发送数据的每个字节（8位字节）都标上号码的编号（序列号的初始值并非为0。而是在建立连接以后由随机数生成。而后面的计算则是对每一字节加一）。接收端查询接收数据TCP首部中序列号和数据的长度，将自己下一步应该接收的序列号作为确认硬代返送回去。这样，通过序列号和确认应答号，TCP可以实现可靠传输。

序列号（或确认应答号）也指字节与字节之间的分隔。

TCP的数据长度并未写入TCP首部。实际通信中求得TCP包的长度的计算公式是：IP首部中的数据包长度-IP首部长度TCP首部长度。


![](TCP_files/5976b2a0ab64413383000a7d.png)

----------


# 重发超时如何确定

TCP时时刻刻都要保持高可靠的通讯。它在每次发包时都会计算``往返时间（RTT Round Trip Time 指报文段的往返时间）`及`偏差（RTT时间波动的值，方差。有时也叫抖动）`。将这个往返时间和偏差相加重发超时的时间，就是比这个综合要稍大一点的值。

> 如果数据被超时重传后还是接收不到应答,则要进行再次发送。此时，等待确认的时间会以2倍，4倍的指数函数延长。

----------


# 连接管理

TCP的一个连接的建立至少需要来回发送7个包才能完成。
![](TCP_files/5976b660ab64413383000b1f.png)

----------


# TCP以段为单位发送数据

在建立TCP连接的同时，也可以确定发送数据包的单位，我们也可以称其为“最大消息长度”（MSS：Maximum Segment Size）。最理想的情况是，最大消息长度正好是IP中不会被分片处理的最大数据长度。

TCP在传输大朗数据时，是以MSS的大小将数据进行分割发送的。进行重发时也是以MSS为单位。

两端的主机在发出建立连接的请求时，会在TCP首部中写入MSS选项，告诉对方自己的接口能够适应的MSS的大小（为附加MSS选项，TCP首部将不再是20字节，而是4字节的整数倍）。然后会在两者之间选择一个较小的值来使用（在建立连接时，如果某一方的MSS选项被省略，可以选为IP包的长度不超过576字节的值（IP首部20字节，TCP首部20字节，MSS 536字节））。

![](TCP_files/5976e4e9ab644135b40010e2.png)

----------


# 窗口控制提高速度
窗口大小就是指无需等待确认应答而可以继续发送数据的最大值。如下图中，窗口大小为4个段。

这个机制实现了使用大量的缓冲区（Buffer 在此处标识临时保存收发数据的场所。通常是在计算机内存中开辟的一部分空间），通过对多个段同时进行确认应答的功能。
![](TCP_files/5976e4e8ab644135b40010df.png)

在滑动窗口以外的部分包括尚未发送的数据以及已经确认对端已收到的数据。当数据发出后若如期收到确认应答就可以不用再重发，此时数据皆可以从缓冲区清除。

收到确认应答，将窗口滑动到确认硬代中的需要的位置。这样可以顺序地将多个段同时发送提高通信性能。这种机制也被称为滑动窗口控制。

![](TCP_files/5976e4e9ab644135b40010e4.png)

----------


# 窗口控制和重发控制
- 考虑确认应答未能返回的情况。
这种情况下，数据已经达到对端，是不需要进行重发的。然而，在没有使用窗口控制的时候，没有收到确认应答的数据会被重发。而使用了窗口控制，如下图，某些确认应答即便丢失也无需重发。
![](TCP_files/5976e4e8ab644135b40010e0.png)

- 某个报文段丢失的情况。
接收主机如果收到一个自己应该接收的序号以外的数据时，会针对当前位置收到数据返回确认应答（不过即使接收端主机收到的包序号并不连续，也不会将数据丢弃而是暂时保存至缓冲区中）。
当某一报文段丢失后，发送端会一直收到序号为1001的确认应答，这个确认应答好像在提醒发送端“我想接收的是从1001开始的数据”。因此，在窗口比较大，又出现报文段丢失的情况下，同一个序号的确认应答将会被重复不断地返回。而发送端主机如果连续3次收到同一个确认应答（之所以连续收到3次而不是两次的理由是因为，即使数据段的序号被替换两次也不会触发重发机制）。就会将其所对应的数据进行重发。这种机制比之前提到的超时管理更加高效，因此也被称作高速重发控制。
![](TCP_files/5976e4e8ab644135b40010de.png)

----------


# 流控制

发送端根据自己的实际情况发送数据。但是，接收端可能收到的是一个毫无关系的数据包有可能会在处理其他问题上花费一些时间。因此在为这个数据包做其他处理时会耗费一些时间，甚至在高负荷情况下无法接收任何数据。如此一来，如果接收端将本应该接收的数据丢弃的话，就又会触发重发机制，从而导致网络流量的浪费。

为了防止这种现象发生，TCP提供一种机制可以让发送端根据接收端的实际接收能力控制发送的数据量。这就是所谓的流控制。它的具体操作时，接收端主机向发送端主机通知自己可以接收数据的大小，于是发送端会发送不超过这个限制的数据。该大小限度就被称为窗口大小。

TCP首部中，专门有一个字段用来通知窗口大小。接收主机将自己的可以接收的缓冲区大小放入这个字段通知给发送端。这个值越大，说明网络的吞吐量越高。

不过，接收端这个缓冲区一旦面临数据溢出时，窗口大小的值也会随之被设置为一个更小的值通知给发送端，从而控制数据发送量。就是说，发送端主机会根据接收端主机的指示，对发送数据的量进行控制。这也形成了一个完整的TCP流控制（流量控制）。
 ![](TCP_files/5976e4e9ab644135b40010e3.png)

----------


# 拥塞控制
网络中的链路容量和交换结点中的缓存和处理机都有着工作的极限，当网络的需求超过它们的工作极限时，就出现了拥塞。拥塞控制就是防止过多的数据注入到网络中，这样可以使网络中的路由器或链路不致过载。常用的方法就是：

 1. 慢开始、拥塞控制
 2. 快重传、快恢复

一切的基础还是慢开始，这种方法的思路是这样的：

- 发送方维持一个叫做“拥塞窗口”的变量，该变量和接收端口共同决定了发送者的发送窗口；
- 当主机开始发送数据时，避免一下子将大量字节注入到网络，造成或者增加拥塞，选择发送一个1字节的试探报文；
- 当收到第一个字节的数据的确认后，就发送2个字节的报文；
- 若再次收到2个字节的确认，则发送4个字节，依次递增2的指数级；
- 最后会达到一个提前预设的“慢开始门限”，比如24，即一次发送了24个分组，此时遵循下面的条件判定：
    - cwnd < ssthresh， 继续使用慢开始算法；
    - cwnd > ssthresh，停止使用慢开始算法，改用拥塞避免算法；
    - cwnd = ssthresh，既可以使用慢开始算法，也可以使用拥塞避免算法；
- 所谓拥塞避免算法就是：每经过一个往返时间RTT就把发送方的拥塞窗口+1，即让拥塞窗口缓慢地增大，按照线性规律增长；
- 当出现网络拥塞，比如丢包时，将慢开始门限设为原先的一半，然后将cwnd设为1，执行慢开始算法（较低的起点，指数级增长）；
![](TCP_files/5976fe74ab64413383001469.png)

上述方法的目的是在拥塞发生时循序减少主机发送到网络中的分组数，使得发生拥塞的路由器有足够的时间把队列中积压的分组处理完毕。慢开始和拥塞控制算法常常作为一个整体使用，而快重传和快恢复则是为了减少因为拥塞导致的数据包丢失带来的重传时间，从而避免传递无用的数据到网络。快重传的机制是：

- 接收方建立这样的机制，如果一个包丢失，则对后续的包继续发送针对该包的重传请求；
- 一旦发送方接收到三个一样的确认，就知道该包之后出现了错误，立刻重传该包；
- 此时发送方开始执行“快恢复”算法：
    - 慢开始门限减半；
    - cwnd设为慢开始门限减半后的数值；
    - 执行拥塞避免算法（高起点，线性增长）；
![](TCP_files/5976fe8fab64413383001471.png)

# 提高网络利用率的规范
## Nagle算法(转自[Nagle 算法](http://blog.163.com/li_xiang1102/blog/static/607140762011111103213616/))
TCP/IP协议中，无论发送多少数据，总是要在数据前面加上协议头，同时，对方接收到数据，也需要发送ACK表示确认。为了尽可能的利用网络带宽，TCP总是希望尽可能的发送足够大的数据。（一个连接会设置MSS参数，因此，TCP/IP希望每次都能够以MSS尺寸的数据块来发送数据）。Nagle算法就是为了尽可能发送大块数据，避免网络中充斥着许多小数据块。

> Nagle算法的基本定义是任意时刻，最多只能有一个未被确认的小段。 所谓“小段”，指的是小于MSS尺寸的数据块，所谓“未被确认”，是指一个数据块发送出去后，没有收到对方发送的ACK确认该数据已收到。

Nagle算法的规则（可参考tcp_output.c文件里tcp_nagle_check函数注释）：

    如果包长度达到MSS，则允许发送；
    如果该包含有FIN，则允许发送；
    设置了TCP_NODELAY选项，则允许发送；
    未设置TCP_CORK选项时，若所有发出去的小数据包（包长度小于MSS）均被确认，则允许发送；
    上述条件都未满足，但发生了超时（一般为200ms），则立即发送。


> Nagle算法只允许一个未被ACK的包存在于网络，它并不管包的大小，因此它事实上就是一个扩展的停-等协议，只不过它是基于包停-等的，而不是基于字节停-等的。Nagle算法完全由TCP协议的ACK机制决定，这会带来一些问题，比如如果对端ACK回复很快的话，Nagle事实上不会拼接太多的数据包，虽然避免了网络拥塞，网络总体的利用率依然很低。



</pre>
			<!-- html -->
			<div class="content-html" id="leanote-content-html"></div>
		</div>
	</div>

<!-- 该js供其它处理 -->
<script src="../leanote-markdown.js"></script>
<script src="http://leanote.github.io/markdown-to-html/markdown-to-html.min.js"></script>
<script>
function init() {
	markdownToHtml(document.getElementById('leanote-content-markdown').value, document.getElementById('leanote-content-html'), function(html) {
		// 解析后执行
		if(window.markdownParsed) {
			window.markdownParsed(html);
		}
	});
	var $m = document.getElementById('tab-markdown');
	var $h = document.getElementById('tab-html');
	var $cc = document.getElementById('content-container');
	function toggleToHtml(isToHtml) {
		$cc.className = isToHtml ? 'content-container html' : 'content-container';
	}
	$m.addEventListener('click', function() {
		toggleToHtml(false);
	});
	$h.addEventListener('click', function() {
		toggleToHtml(true);
	});
}

// 如果不要自动解析html, notParseMarkdown在leanote-markdown.js中定义
if(!window.notParseMarkdown) {
	init();
}
</script>
</body>
</html>